﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Locator.LcboServices.LcboProductsService.Interface.Services;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using Locator.LcboServices.LcboProductsService;
using Locator.LcboServices.PageLoaders.Services;
using System.Diagnostics;
using DataAccess.Interface;
using DataAccess;
using DataAccess.BusinessEntities;
using Locator.LcboServices.BusinessEntities;

namespace ProductScanner
{
    class Program
    {
        private const string containerIdFormat = "{0}x{1:0.00}";
        private static ILcboProductsService lcboProductsService;
        private static IUnitOfWork unitOfWork;

        static void Main(string[] args)
        {
            //lcboProductsService = new LcboProductServiceAgent(new TestPageRetrieverFactory());
            lcboProductsService = new LcboProductServiceAgent(new LivePageRetrieverFactory());
            Stopwatch getProductsStopwatch = new Stopwatch();
            getProductsStopwatch.Start();
            unitOfWork = new SqlUnitOfWork();
            var lcboProducts = lcboProductsService.GetAllProducts();

            var substanceDict = BuildSubstanceDictionaryAndUpdateDbIfNeeded(lcboProducts);
            var containerDict = BuildContainerDictionaryAndUpdateDbIfNeeded(lcboProducts);
            UpdateProductsInDb(lcboProducts, substanceDict, containerDict);
            getProductsStopwatch.Stop();
            Console.WriteLine(String.Format("Elapsed time in seconds: {0:0.00}", getProductsStopwatch.ElapsedMilliseconds / 1000.00));
        }

        private static Dictionary<string, Substance> BuildSubstanceDictionaryAndUpdateDbIfNeeded(List<LcboProduct> lcboProducts)
        {
            Dictionary<string, Substance> knownSubstancesDictionary = new Dictionary<string, Substance>();
            IEnumerable<string> allLcboSubstanceNames = from p in lcboProducts select p.Name;
            IQueryable<string> knownSubstanceNames = from s in unitOfWork.Substances.FindAll() select s.Name;
            List<string> knownSubstanceNamesList = new List<string>();

            // Force the query to evaluate and put the results in a list
            foreach (string substanceName in knownSubstanceNames)
            {
                knownSubstanceNamesList.Add(substanceName);
            }

            Console.WriteLine(String.Format("There are {0} substances in the DB", knownSubstanceNames.Count()));

            IEnumerable<string> newSubstanceNames = from s in allLcboSubstanceNames.Distinct()
                                                    where !knownSubstanceNamesList.Contains(s)
                                                    select s;

            int newSubstanceCount = 0;
            foreach (var substance in newSubstanceNames)
            {
                Substance newSubstance = new Substance() { Name = substance };
                unitOfWork.Substances.Add(newSubstance);
                newSubstanceCount++;
            }

            Console.WriteLine(String.Format("There are {0} new substances on the LCBO website", newSubstanceCount));

            unitOfWork.Commit();
            Console.WriteLine("Updated DB");


            foreach (Substance substance in unitOfWork.Substances.FindAll())
            {
                knownSubstancesDictionary.Add(substance.Name, substance);
            }

            Console.WriteLine(String.Format("Built dictionary of {0} substances", knownSubstancesDictionary.Count));
            return knownSubstancesDictionary;
        }

        private static void UpdateProductsInDb(List<LcboProduct> lcboProducts, Dictionary<string, Substance> substanceDict, Dictionary<string, Container> containerDict)
        {
            //Dictionary<int, Product> knownProductsDictionary = new Dictionary<int, Product>();
            List<int> knownProductIdsList = new List<int>();

            foreach (var product in unitOfWork.Products.FindAll())
            {
                knownProductIdsList.Add(product.Id);
            }

            Console.WriteLine(String.Format("There are {0} products in the DB", knownProductIdsList.Count()));

            Dictionary<int, LcboProduct> allLcboProducts = new Dictionary<int, LcboProduct>();
            foreach (var lcboProduct in lcboProducts)
            {
                allLcboProducts.Add(lcboProduct.Id, lcboProduct);
            }

            IEnumerable<LcboProduct> newProducts = from c in allLcboProducts.Values
                                                   where !knownProductIdsList.Contains(c.Id)
                                                   select c;

            int newProductCount = 0;
            foreach (var lcboProduct in newProducts)
            {
                Product product = new Product()
                {
                    Id = lcboProduct.Id,
                    Substance = substanceDict[lcboProduct.Name],
                    Container = containerDict[String.Format(containerIdFormat, lcboProduct.Container.NumberOfContainers, lcboProduct.Container.ContainerCapacity)]
                };

                unitOfWork.Products.Add(product);
                newProductCount++;
            }

            unitOfWork.Commit();
            Console.WriteLine(String.Format("There are {0} new products on the LCBO website", newProducts.Count()));
        }

        private static Dictionary<string, Container> BuildContainerDictionaryAndUpdateDbIfNeeded(List<LcboProduct> lcboProducts)
        {
            Dictionary<string, Container> knownContainerDictionary = new Dictionary<string, Container>();
            List<string> knownContainerIdsList = new List<string>();

            // Force the query to evaluate and put the results in a list
            foreach (var container in unitOfWork.Containers.FindAll())
            {
                string containerKey = String.Format(containerIdFormat, container.NumberOfContainers, container.ContainerCapacity);
                knownContainerIdsList.Add(containerKey);
            }

            Dictionary<string, LcboContainerFormat> uniqueLcboContainers = new Dictionary<string, LcboContainerFormat>();
            foreach (LcboProduct lcboProduct in lcboProducts)
            {
                string lcboContainerKey = String.Format(containerIdFormat, lcboProduct.Container.NumberOfContainers, lcboProduct.Container.ContainerCapacity);
                if (!uniqueLcboContainers.ContainsKey(lcboContainerKey))
                {
                    uniqueLcboContainers.Add(lcboContainerKey, lcboProduct.Container);
                }
            }

            Console.WriteLine(String.Format("There are {0} containers in the DB", knownContainerIdsList.Count()));

            IEnumerable<LcboContainerFormat> newContainers = from c in uniqueLcboContainers.Values
                                                             where !knownContainerIdsList.Contains(String.Format(containerIdFormat, c.NumberOfContainers, c.ContainerCapacity))
                                                             select c;

            int newContainerCount = 0;
            foreach (var lcboContainer in newContainers)
            {
                Container newContainer = new Container()
                {
                    ContainerCapacity = lcboContainer.ContainerCapacity,
                    NumberOfContainers = lcboContainer.NumberOfContainers
                };

                unitOfWork.Containers.Add(newContainer);
                newContainerCount++;
            }

            Console.WriteLine(String.Format("There are {0} new containers on the LCBO website", newContainerCount));

            unitOfWork.Commit();
            Console.WriteLine("Updated DB");

            foreach (Container container in unitOfWork.Containers.FindAll())
            {
                knownContainerDictionary.Add(String.Format(containerIdFormat, container.NumberOfContainers, container.ContainerCapacity), container);
            }

            Console.WriteLine(String.Format("Built dictionary of {0} containers", knownContainerDictionary.Count));
            return knownContainerDictionary;
        }
    }
}
